/**
 *  Serponix is an arcade game in focus to multiplayer based on the classic game Snake.
 *  Copyright (C) 2010 - 2011  Daniel Vala
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License,
 *  or  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.

 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  If you have any question do not hesitate to contact author
 *  on e-mail address: danielvala42@gmail.com
 */
package com.serponix.net;

import com.serponix.gui.lobby.ModelLobbyServer;
import com.serponix.settings.SettingsFactory;
import com.serponix.game.*;
import java.awt.Dimension;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

/**
 * Obsluhuje výměnu dat se všemi připojenými klienty
 * @author Daniel Vala
 */
public class UDPServer implements Server {

    private ModelLobbyServer modelLobby;
    private DatagramSocket socket;
    private GameModel model;
    private List<Player> hraciKlienti;
    private boolean prijemLobby;
    private boolean cancel;

    public UDPServer(ModelLobbyServer modelLobby) {
        this.modelLobby = modelLobby;
        prijemLobby = true;
        hraciKlienti = new ArrayList<Player>();

        try {
            socket = new DatagramSocket(SettingsFactory.getNastaveniHry().getPort());
        } catch (SocketException ex) {
            try {
                JOptionPane.showMessageDialog(null, "Na Vaší IP adrese " + InetAddress.getLocalHost().getHostAddress() + " a nastaveném portu " + SettingsFactory.getNastaveniHry().getPort() + " je již hra vytvořena. \n Nebo tento port používá jiná aplikace.", "Chyba", JOptionPane.ERROR_MESSAGE);
            } catch (UnknownHostException ex2) {
                System.err.println("Chyba :" + ex2.getMessage());
            }
            System.exit(1);
        }
    }

    @Override
    public void setModel(GameModel model) {
        this.model = model;
    }

    @Override
    public void odpojSe() {
        poslatData(Informace.KONEC + Informace.ODDELOVAC1);
        cancel = true;
        if (socket != null) {
            socket.close();
        }
    }

    @Override
    public void run() {
        prijemStavuLobby();
        prijemDat();
    }

    @Override
    public void odstartujHru(int pocetHracu, Dimension velikostHerniPlochy, Mapa mapa) {
        prijemLobby = false;
        String zpravaKOdeslani = Informace.START + Informace.ODDELOVAC1;
        zpravaKOdeslani += Informace.POCET_HRACU + Informace.ODDELOVAC3 + pocetHracu + Informace.ODDELOVAC2;
        zpravaKOdeslani += Informace.VELIKOST_HERNI_PLOCHY + Informace.ODDELOVAC3 + (int) velikostHerniPlochy.getWidth() + Informace.ODDELOVAC3 + (int) velikostHerniPlochy.getHeight() + Informace.ODDELOVAC2;
        zpravaKOdeslani += mapa.getInformaceProKresleni() + Informace.ODDELOVAC2;
        poslatData(zpravaKOdeslani);
    }

    /**
     * Přijímá zprávy od klientů ve stavu lobby, které následně zpracuje.
     */
    @Override
    public void prijemStavuLobby() {
        while (prijemLobby) {
            try {
                byte[] data = new byte[100];
                DatagramPacket receivedPacket = new DatagramPacket(data, data.length);
                socket.receive(receivedPacket);
                String prijataZprava;
                try {
                    prijataZprava = new String(receivedPacket.getData(), 0, receivedPacket.getLength(), "UTF-8");
                } catch (UnsupportedEncodingException ex) {
                    throw new RuntimeException(ex);
                }
                StringTokenizer st = new StringTokenizer(prijataZprava, Informace.ODDELOVAC1);
                int druhZpravy = 0;
                try {
                    druhZpravy = Integer.parseInt(st.nextToken());
                } catch (NumberFormatException ex) {
                    System.err.println("Příjem nerozeznatelného pokynu od serveru:");
                    System.err.println(prijataZprava);
                }

                switch (druhZpravy) {
                    case Informace.ZPRAVA:
                        String informace = st.nextToken();
                        zobrazInformaci(informace);
                        poslatData(Informace.ZPRAVA + Informace.ODDELOVAC1 + informace);
                        break;
                    case Informace.NOVY_HRAC:
                        Player hrac = new Player(st.nextToken(), Player.Control.CLIENT, receivedPacket);
                        hraciKlienti.add(hrac);
                        boolean uspech = modelLobby.addHrac(hrac);
                        if (uspech) {
                            String zprava = "Hráč " + hrac + " se připojil do hry.";
                            zobrazInformaci(zprava);
                            poslatData(Informace.ZPRAVA + Informace.ODDELOVAC1 + zprava);
                            modelLobby.posliVsechnyInformace(receivedPacket);
                        } else {
                            poslatData(Informace.PLNY_SERVER + "");
                        }
                        break;
                    case Informace.HLEDANI:
                        byte[] zprava = (Informace.PRIJEM + Informace.ODDELOVAC1 + InetAddress.getLocalHost()).getBytes();
                        socket.send(new DatagramPacket(zprava, zprava.length, receivedPacket.getAddress(), receivedPacket.getPort()));
                        break;
                    case Informace.KONEC:
                        if (prijemLobby) {
                            Player odpojenyHrac = najdiHrace(receivedPacket.getAddress());
                            if (odpojenyHrac != null) {
                                zobrazInformaci("Hráč " + odpojenyHrac + " se odpojil.");
                                modelLobby.odstranHrace(odpojenyHrac);
                                hraciKlienti.remove(odpojenyHrac);
                            }
                        }
                        break;
                    default:
                        System.err.println("Příjem nerozeznatelného pokynu od serveru.");
                }
            } catch (SocketException ex) {
                if (!cancel) {
                    JOptionPane.showMessageDialog(null, "Neočekávané uzavření socketu.", "Chyba spojení", JOptionPane.ERROR_MESSAGE);
                }
                return;
            } catch (IOException ex) {
                JOptionPane.showMessageDialog(null, "Chyba při čtení ze sítě.", "Chyba spojení", JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    private Player najdiHrace(InetAddress address) {
        for (int i = 0; i < hraciKlienti.size(); i++) {
            if (hraciKlienti.get(i).getPacket().getAddress().equals(address)) {
                return hraciKlienti.get(i);
            }
        }
        return null;
    }

    /**
     * Přijímá zprávy od klientů, které následně zpracuje.
     */
    @Override
    public void prijemDat() {
        while (true) {
            byte[] data = new byte[20000];
            DatagramPacket receivedPacket = new DatagramPacket(data, data.length);
            try {
                socket.receive(receivedPacket);
            } catch (SocketException ex) {
                if (!cancel) {
                    JOptionPane.showMessageDialog(null, "Neočekávané uzavření socketu.", "Chyba spojení", JOptionPane.ERROR_MESSAGE);
                    model.ukonciHru();
                }
                return;
            } catch (IOException e) {
                zobrazInformaci(e.toString());
            }
            String prijataZprava;
            try {
                prijataZprava = new String(receivedPacket.getData(), 0, receivedPacket.getLength(), "UTF-8");
            } catch (UnsupportedEncodingException ex) {
                throw new RuntimeException();
            }
            StringTokenizer st = new StringTokenizer(prijataZprava, Informace.ODDELOVAC1);
            int druhZpravy = 0;
            try {
                druhZpravy = Integer.parseInt(st.nextToken());
            } catch (NumberFormatException ex) {
                System.err.println("Příjem nerozeznatelného pokynu od serveru.");
            }

            for (int i = 0; i < hraciKlienti.size(); i++) {


                switch (druhZpravy) {
                    case Informace.NAHORU:
                        if (hraciKlienti.get(i).getPacket().getAddress().equals(receivedPacket.getAddress())) {
                            hraciKlienti.get(i).changeSmer(Direction.UP);
                        }
                        break;
                    case Informace.DOLU:
                        if (hraciKlienti.get(i).getPacket().getAddress().equals(receivedPacket.getAddress())) {
                            hraciKlienti.get(i).changeSmer(Direction.DOWN);
                        }
                        break;
                    case Informace.VLEVO:
                        if (hraciKlienti.get(i).getPacket().getAddress().equals(receivedPacket.getAddress())) {
                            hraciKlienti.get(i).changeSmer(Direction.LEFT);
                        }
                        break;
                    case Informace.VPRAVO:
                        if (hraciKlienti.get(i).getPacket().getAddress().equals(receivedPacket.getAddress())) {
                            hraciKlienti.get(i).changeSmer(Direction.RIGHT);
                        }
                        break;
                    case Informace.STREL:
                        if (hraciKlienti.get(i).getPacket().getAddress().equals(receivedPacket.getAddress())) {
                            hraciKlienti.get(i).getSnake().strelLaser();   // null pointer
                        }
                        break;
                    case Informace.STRELR:
                        if (hraciKlienti.get(i).getPacket().getAddress().equals(receivedPacket.getAddress())) {
                            hraciKlienti.get(i).getSnake().strelRaketu();
                        }
                        break;
                    case Informace.PAUZA:
                        if (hraciKlienti.get(i).getPacket().getAddress().equals(receivedPacket.getAddress())) {
                            hraciKlienti.get(i).pause();
                        }
                        break;
                    case Informace.KONEC:
                        if (hraciKlienti.get(i).getPacket().getAddress().equals(receivedPacket.getAddress())) {
                            Player hrac = najdiHrace(receivedPacket.getAddress());
                            if (hrac != null) {
                                zobrazInformaci("Hráč " + hrac + " se odpojil. Ovládání jeho hada převzala umělá inteligence.");
                                hrac.zapniAI();
                            }
                        }
                        break;
                    default:
                        System.err.println("Příjem nerozeznatelného pokynu od serveru.");
                }
            }
        }
    }

    @Override
    public void poslatData(String zprava) {
        byte[] data;
        try {
            data = zprava.getBytes("UTF-8");
        } catch (UnsupportedEncodingException ex) {
            throw new RuntimeException(ex);
        }
        DatagramPacket packetKOdeslani;
        for (int i = 0; i < hraciKlienti.size(); i++) {
            packetKOdeslani = new DatagramPacket(data, data.length, hraciKlienti.get(i).getPacket().getAddress(), hraciKlienti.get(i).getPacket().getPort());
            try {
                socket.send(packetKOdeslani);
            } catch (IOException ex) {
                System.err.println("IO Exception");
            }
        }
    }

    @Override
    public void posliDataKlientovi(String zprava, DatagramPacket packet) {
        byte[] data;
        try {
            data = zprava.getBytes("UTF-8");
        } catch (UnsupportedEncodingException ex) {
            throw new RuntimeException(ex);
        }
        DatagramPacket packetKOdeslani;
        packetKOdeslani = new DatagramPacket(data, data.length, packet.getAddress(), packet.getPort());
        try {
            socket.send(packetKOdeslani);
        } catch (IOException ex) {
            System.err.println("IO Exception");
        }


    }

    public void posliDataVsemKlientumKrome(String zprava, DatagramPacket packet) {
        byte[] data;
        try {
            data = zprava.getBytes("UTF-8");
        } catch (UnsupportedEncodingException ex) {
            throw new RuntimeException(ex);
        }
        DatagramPacket packetKOdeslani;
        for (int i = 0; i < hraciKlienti.size(); i++) {
            if (!hraciKlienti.get(i).getPacket().getAddress().equals(packet.getAddress())) {
                packetKOdeslani = new DatagramPacket(data, data.length, hraciKlienti.get(i).getPacket().getAddress(), hraciKlienti.get(i).getPacket().getPort());
                try {
                    socket.send(packetKOdeslani);
                } catch (IOException ex) {
                    System.err.println("IO Exception");
                }
            }
        }
    }

    /**
     * zobrazí informaci buď v lobby, nebo ve hře
     * @param messageToDisplay
     */
    private void zobrazInformaci(final String messageToDisplay) {
        SwingUtilities.invokeLater(
                new Runnable() {

            @Override
                    public void run() {
                        if (model != null) {
                            model.getPanelZprav().addMessage(String.format(messageToDisplay));
                        } else {
                            modelLobby.zobrazZpravu(messageToDisplay);
                        }
                    }
                });
    }
}
